package marchsoft.modules.four.h5.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import marchsoft.annotation.rest.AnonymousDeleteMapping;
import marchsoft.annotation.rest.AnonymousGetMapping;
import marchsoft.annotation.rest.AnonymousPostMapping;
import marchsoft.config.bean.RsaProperties;
import marchsoft.enums.ResultEnum;
import marchsoft.modules.four.common.mapper.EStudentMapper;
import marchsoft.modules.four.common.utils.BaseUtils;
import marchsoft.modules.four.h5.entity.bo.EStudentBO;
import marchsoft.modules.four.h5.mapper.HomeMapper;
import marchsoft.modules.security.config.bean.LoginCodeEnum;
import marchsoft.modules.security.config.bean.LoginProperties;
import marchsoft.modules.security.config.bean.SecurityProperties;
import marchsoft.modules.security.entity.dto.AuthUserDto;
import marchsoft.modules.security.entity.dto.JwtFrontUserDTO;
import marchsoft.modules.security.security.TokenProvider;
import marchsoft.modules.security.service.FrontUserDetailsServiceImpl;
import marchsoft.modules.security.service.OnlineUserService;
import marchsoft.modules.system.entity.Dept;
import marchsoft.response.Result;
import marchsoft.utils.CacheKey;
import marchsoft.utils.RedisUtils;
import marchsoft.utils.RsaUtils;
import marchsoft.utils.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName TestController
 * @Author ZhuGuangLiang <786945363@qq.com>
 * @Date 2022/04/04 08:41
 */
@RestController
@RequiredArgsConstructor
@Slf4j
@Api(tags = "H5端-登录模块")
@RequestMapping("/api/h5")
public class LoginController {
	private final SecurityProperties properties;
	private final RedisUtils redisUtils;
	private final OnlineUserService onlineUserService;
	private final TokenProvider tokenProvider;
	private final LoginProperties loginProperties;
	private final FrontUserDetailsServiceImpl userDetailsService;
	private final EStudentMapper eStudentMapper;
	private final DaoAuthenticationProvider authenticationProvider;
	private final AuthenticationManagerBuilder authenticationManagerBuilder;
	private final HomeMapper loginMapper;

	@ApiOperation("查询所有部门")
	@AnonymousGetMapping("/all/dept")
	public Result<List<Dept>> getAllDept(){
		return Result.success(loginMapper.selectAllDept());
	}

	@ApiOperation("登录授权")
	@AnonymousPostMapping(value = "/login")
	public Result<Map<String, Object>> loginReception(@Validated @RequestBody AuthUserDto authUser,
                                                      HttpServletRequest request) throws Exception {
		if (ObjectUtil.isNull(authUser.getDeptId())) {
			BaseUtils.errorLog(ResultEnum.OPERATION_FAIL, "用户部门不能为null", authUser);
		}
		// 密码解密
		String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
//		String password=authUser.getPassword();
		boolean hasVerifyCode = false;
		{
			if (StrUtil.isNotEmpty(authUser.getCode()) && StrUtil.isNotEmpty(authUser.getUuid())) {
				hasVerifyCode = true;
			}
			//校验验证码失败的次数  大于三次进行校验
			String codeKey = getCodeKey(request, authUser.getUsername());
			Map<String, Object> result = judgeIsVerify(codeKey, authUser, hasVerifyCode);
			//验证不同过
			if (ObjectUtil.isNotNull(result)) {
				return Result.error(result.get("failReason").toString(), result);
			}
		}
		EStudentBO eStudentBO = eStudentMapper.selectIdByDepStu(authUser.getDeptId(),authUser.getUsername());
		Long userId=eStudentBO.getId();
		authenticationProvider.setUserDetailsService(userDetailsService);
		UsernamePasswordAuthenticationToken authenticationToken =
				new UsernamePasswordAuthenticationToken(userId, password);
		Authentication authentication;
		try {
			authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
		} catch (BadCredentialsException e) {
			//账号或密码错误
			Map<String, Object> notPassMap = failLoginRes(getCodeKey(request, authUser.getUsername()), hasVerifyCode, "帐号或密码错误");
			return Result.error(notPassMap.get("failReason").toString(), notPassMap);
		} catch (InternalAuthenticationServiceException e) {
			//账户不存在
			return Result.error("帐号未激活", failLoginRes(getCodeKey(request, authUser.getUsername()), hasVerifyCode, "帐号未激活"));
		}
		SecurityContextHolder.getContext().setAuthentication(authentication);
		// 生成令牌
		final JwtFrontUserDTO jwtUserDto = (JwtFrontUserDTO) authentication.getPrincipal();
		String token = tokenProvider.createToken(authentication, jwtUserDto.getUser().getId());
		// 保存在线信息
		onlineUserService.saveFrontUser(jwtUserDto, token, request);
		// 返回 token 与 用户信息
		//成功    返回 token 与 用户信息
		Map<String, Object> authInfo = getSuccessLoginResAndVerifyCode(properties.getTokenStartWith() + token, jwtUserDto, authUser.getUsername());
		return Result.success(authInfo);
	}
	private String getCodeKey(HttpServletRequest request, String username) {
		return StringUtils.getIp(request) + username;
	}

	/**
	 * @param codeKey
	 * @param authUser
	 * @return java.util.Map<java.lang.String, java.lang.Object>
	 * @description: 验证码校验
	 * @date: 2021/8/29 19:03
	 * @author: zhq
	 */
	public Map<String, Object> judgeIsVerify(String codeKey, AuthUserDto authUser, boolean hasVerifyCode) {
		String failUserKey = CacheKey.AfterUSER_FAIL_LOG + codeKey;
		String value = String.valueOf(redisUtils.get(failUserKey));
		int failNum = Integer.valueOf(value == "null" ? "0" : value);
		//超过验证次数
		if (failNum >= 3 || hasVerifyCode) {
			//进行验证码校验
			// 查询验证码
			String code = (String) redisUtils.get(authUser.getUuid());
			// 清除验证码
			redisUtils.del(authUser.getUuid());
			if (StrUtil.isBlank(code)) {
				return getFailLoginResAndVerifyCode(true, "验证码不存在或已过期");
			}
			if (StrUtil.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
				return getFailLoginResAndVerifyCode(true, "验证码错误");
			}
		}
		return null;
	}

	public Map<String, Object> getFailLoginResAndVerifyCode(boolean isHasVerifyCode, String failReason) {
		Captcha captcha = null;
		String uuid = "";
		if (isHasVerifyCode) {
			captcha = loginProperties.getCaptcha();
			while (Double.parseDouble(captcha.text()) < 0) {
				captcha = loginProperties.getCaptcha();
			}
			String captchaValue = captcha.text();
			uuid = properties.getCodeKey() + IdUtil.simpleUUID();
			//当验证码类型为 arithmetic时且长度 >= 2 时，captcha.text()的结果有几率为浮点型
			if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() & captchaValue.contains(".")) {
				captchaValue = captchaValue.split("\\.")[0];
			}
			// 保存
			redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
		}
		Map<String, Object> loginRes = new HashMap<>();
		loginRes.put("loginStatus", false);
		loginRes.put("isVerify", isHasVerifyCode);
		loginRes.put("img", isHasVerifyCode ? captcha.toBase64() : "");
		loginRes.put("uuid", uuid);
		loginRes.put("failReason", failReason);
		return loginRes;
	}

	/**
	 * @param
	 * @return java.util.Map<java.lang.String, java.lang.String>
	 * @description: 登陆成功  返回token和用户信息
	 * @date: 2021/8/29 12:25
	 * @author: zhq
	 */
	public Map<String, Object> getSuccessLoginResAndVerifyCode(String token, JwtFrontUserDTO jwtUserDto, String username) {
		String failUserKey = CacheKey.AfterUSER_FAIL_LOG + username;
		redisUtils.del(failUserKey);
		Map<String, Object> loginRes = new HashMap<>();
		loginRes.put("token", token);
		loginRes.put("user", jwtUserDto);
		loginRes.put("loginStatus", true);
		return loginRes;
	}

	public Map<String, Object> failLoginRes(String codeKey, boolean hasVerifyCode, String failReason) {
		String failUserKey = CacheKey.AfterUSER_FAIL_LOG + codeKey;
		long time = 60 * 5;
		String value = String.valueOf(redisUtils.get(failUserKey));
		int failNum = Integer.valueOf(value == "null" ? "0" : value);
		if (redisUtils.hasKey(failUserKey)) {
			if (failNum >= 2) {
				return getFailLoginResAndVerifyCode(true, failReason);
			} else {
				redisUtils.set(failUserKey, ++failNum, time);
				return getFailLoginResAndVerifyCode(hasVerifyCode, failReason);
			}
		} else {
			redisUtils.set(failUserKey, 1, time);
			return getFailLoginResAndVerifyCode(hasVerifyCode, failReason);
		}
	}

	@ApiOperation("退出登录")
	@AnonymousDeleteMapping(value = "/logout")
	public Result<Void> logout(HttpServletRequest request) {
		onlineUserService.logout(tokenProvider.getToken(request));
		return Result.success();
	}
}
